/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package ssp.strategy.agent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.logging.Level;
import ssp.agent.QualityDemand;
import ssp.database.DatabaseManager;
import ssp.database.ServiceMap;
import ssp.event.DepartureEvent;
import ssp.event.EventQueue;
import ssp.net.NWcomponent;
import ssp.net.NWcomponentManager;
import ssp.service.FrontendServer;
import ssp.topology.Node;
import ssp.utility.LoggingManager;
import ssp.utility.MapValueAscending;
import ssp.utility.Pair;
import ssp.utility.PropertyManager;

/**
 *　Agent_v1クラスのStrategyを実際に実現したクラス<br>
 * 比較手法として最短経路選択を実装している
 * @author yamazaki
 */
public class Agent_v1_Strategy_v2 extends Agent_v1_Strategy {

    @Override
    public List<FrontendServer> searchFrontendServer(QualityDemand qualityDemand, NWcomponent belongingNWcomponent, int hopNum, int chargeNum) {
        //ユーザが属するネットワークコンポーネントからNホップ以内のフロントエンドサーバを全て取得
        List<FrontendServer> frontendServersFromNetworkMap = new ArrayList<>(DatabaseManager.getINSTANCE().getNetworkMap().getNearbyServer(belongingNWcomponent.getID(), hopNum));
        
        //サービスマップから取得したフロントエンドサーバのIDと残り容量の逆数を保持するためのリスト
        Map<Integer, Double> frontendServersFromServiceMap = new TreeMap<>();
        
        ServiceMap svMap = DatabaseManager.getINSTANCE().getServiceMap();
        for(int frontednServerID : DatabaseManager.getINSTANCE().getServiceMap().getFrontendServierList(qualityDemand.getTypeID())) {
            if(svMap.getAvailability(frontednServerID) < svMap.getWorkload(new Pair<>(frontednServerID, qualityDemand.getTypeID())) + PropertyManager.getValueAsDouble("FrontendServerMargin")) {
                continue;
            }
            boolean qosFlg = true;
            int qosNum = qualityDemand.getServiceQualityList().size();
            for(int i=0 ; i<qosNum ; i++) {
                if(svMap.getQosIndex(new Pair<>(frontednServerID, qualityDemand.getTypeID()), i) < qualityDemand.getServiceQualityList().get(i)) {
                    qosFlg = false;
                    break;
                }
            }
            if(qosFlg) {
                frontendServersFromServiceMap.put(frontednServerID, svMap.getRecipAvailability(frontednServerID));
            }
        }
        
        TreeMap<Integer, Double> recipAvailabilityMap = new TreeMap<>(new MapValueAscending(frontendServersFromServiceMap));
        recipAvailabilityMap.putAll(frontendServersFromServiceMap);
        
        List<Integer> newList = new ArrayList<>();
        for(int i=0 ; i<chargeNum ; i++) {
            if(recipAvailabilityMap.isEmpty()) {
                break;
            }
            newList.add(recipAvailabilityMap.pollFirstEntry().getKey());
        }
        LoggingManager.logging(Level.FINEST, "The size of frontendServerFromNetworkMap: " + frontendServersFromNetworkMap.size(), new Throwable());
        LoggingManager.logging(Level.FINEST, "The size of frontendServerFromServiceMap: " + frontendServersFromServiceMap.size(), new Throwable());
        List<FrontendServer> intersection = new ArrayList<>();
        for(int id : newList) {
            for(FrontendServer frontendServer : frontendServersFromNetworkMap) {
                if(id == frontendServer.getID()) {
                    intersection.add(frontendServer);
                }
            }
        }
        return intersection;
    }

    @Override
    public Pair<FrontendServer, List<NWcomponent>> searchPath(QualityDemand qualityDemand, NWcomponent belongingNWcomponent, FrontendServer frontendServer) {
        NWcomponent dstNode = (NWcomponent)frontendServer.getConnectedNWcomponent();
        List<NWcomponent> resultPath = dijkstra(belongingNWcomponent, dstNode);
        if(isMeetingDemand(resultPath, qualityDemand) && isMargin(frontendServer, resultPath, qualityDemand)) {
            return new Pair(frontendServer, resultPath);
        }
        return null;
    }

    @Override
    public boolean makeService(FrontendServer frontendServer, List<NWcomponent> nwComponents, QualityDemand demand) {
        double charge = 0.0;
        if(!frontendServer.arrival(frontendServer.getServiceEntry(demand.getTypeID()))) {
            return false;
        }
        charge += frontendServer.getServiceEntry(demand.getTypeID()).getCharge();
        NWcomponent prevNode = null;
        NWcomponent currentNode = null;
        NWcomponent nextNode = null;
        for(NWcomponent node : nwComponents) {
            if(prevNode == null) {
                prevNode = node;
            } else if(currentNode == null) {
                currentNode = node;
                charge += prevNode.getNWresourceManager().getCharge(node, prevNode.getEndsOfInternalPath(node), demand.getBandwidth(), demand.getServiceTimeLength());
                if(!prevNode.getNWresourceManager().reserveResources(node, prevNode.getEndsOfInternalPath(node), demand.getBandwidth())) {
                    return false;
                }
            } else {
                nextNode = node;
                if(!currentNode.getNWresourceManager().reserveResources(prevNode, nextNode, demand.getBandwidth())) {
                    NWcomponent n_prevNode = null;
                    NWcomponent n_currentNode = null;
                    for(NWcomponent n_node : nwComponents) {
                        if(n_prevNode == null) {
                            n_prevNode = n_node;
                        } else if(n_currentNode == null) {
                            n_currentNode = n_node;
                        } else {
                            n_currentNode.getNWresourceManager().releaseResources(n_prevNode, n_node, demand.getBandwidth());
                            if(nextNode.equals(n_node)) {
                                break;
                            }
                            n_prevNode = n_currentNode;
                            n_currentNode = n_node;
                        }
                    }
                    return false;
                }
                charge += currentNode.getNWresourceManager().getCharge(prevNode, nextNode, demand.getBandwidth(), demand.getServiceTimeLength());
                prevNode = currentNode;
                currentNode = nextNode;
            }
        }
        
        //ログ用
        StringBuilder path = new StringBuilder(50);
        
        for(NWcomponent node : nwComponents) {
            path.append(node.getID());
            path.append(", ");
        }
        if(EventQueue.getINSTANCE().isIsLogging())
            LoggingManager.logging(Level.INFO, "FrontendServer: " + frontendServer.getID() + ", path: " + path + " charge: " + charge + " is reserved.", new Throwable());
        
        EventQueue eventQueue = EventQueue.getINSTANCE();
        eventQueue.addEvent(new DepartureEvent(eventQueue.getCurrentTime() + demand.getServiceTimeLength(), frontendServer, nwComponents, demand));
        
        return true;
    }
    
    /**
     * 
     * @param srcNode
     * @param dstNode
     * @return 
     */
    private List<NWcomponent> dijkstra(NWcomponent srcNode, NWcomponent dstNode) {
        Map<Integer, Integer> path = new HashMap<>();
        List<Node> search = new ArrayList<>();
        search.add(srcNode);
        path.put(srcNode.getID(), -1);
        while(true) {
            if(srcNode.getID() == dstNode.getID()) {
                List<NWcomponent> result = new ArrayList<>();
                result.add(dstNode);
                return result;
            }
            List<Node> tmp = new ArrayList<>();
            for(Node node : search) {
                for(Node neighbor : ((NWcomponent)node).getNeighborNWcomponents()) {
                    if(!path.containsKey(((NWcomponent)neighbor).getID()) && !search.contains(neighbor)) {
                        path.put(((NWcomponent)neighbor).getID(), ((NWcomponent)node).getID());
                        if(((NWcomponent)neighbor).getID() == dstNode.getID()) {
                            return backtrace(path, dstNode);
                        }
                        tmp.add(neighbor);
                    }
                }
            }
            search.clear();
            search.addAll(tmp);
        }
    }
    
    /**
     * 
     * @param path
     * @param dstNode
     * @return 
     */
    private List<NWcomponent> backtrace(Map<Integer, Integer> path, NWcomponent dstNode) {
        LinkedList<NWcomponent> result = new LinkedList<>();
        int next = dstNode.getID();
        while(next != -1) {
            result.addFirst(NWcomponentManager.getINSTANCE().getNWcomponentFromID(next));
            next = path.get(next);
        }
        return new ArrayList<>(result);
    }
    
    private boolean isMeetingDemand(List<NWcomponent> path, QualityDemand qualityDeamnd) {
        for(int i=0 ; i<qualityDeamnd.getNetworkQualityList().size() ; i++) {
            double sum = 0;
            int prevNode = -1;
            int currentNode = -1;
            for(NWcomponent node : path) {
                if(prevNode == -1) {
                    prevNode = node.getID();
                    continue;
                } else if(currentNode == -1) {
                    currentNode = node.getID();
                    continue;
                } else {
                    Set<Node> connectedNodes = new HashSet<>();
                    connectedNodes.add(NWcomponentManager.getINSTANCE().getNWcomponentFromID(prevNode));
                    connectedNodes.add(node);
                    Pair<Integer, Set<Node>> key = new Pair(currentNode, connectedNodes);
                    sum += DatabaseManager.getINSTANCE().getNetworkMap().getQoSindex(key, i);
                    prevNode = currentNode;
                    currentNode = node.getID();
                }
            }
            if(qualityDeamnd.getNetworkQualityList().get(i) < sum) {
                return false;
            }
        }
        return true;
    }
    
    private boolean isMargin(FrontendServer frontendServer, List<NWcomponent> path, QualityDemand demand) {
        if(DatabaseManager.getINSTANCE().getServiceMap().getAvailability(frontendServer.getID()) < frontendServer.getServiceEntry(demand.getTypeID()).getWorkload() + PropertyManager.getValueAsDouble("FrontendServerMargin")) {
            return false;
        }
        NWcomponent prevNode = null;
        NWcomponent currentNode = null;
        Set<Node> connectedNodes = new HashSet<>();
        for(NWcomponent node : path) {
            if(prevNode == null) {
                prevNode = node;
            } else if(currentNode == null) {
                currentNode = node;
            } else {
                connectedNodes.add(prevNode);
                connectedNodes.add(node);
                Pair<Integer, Set<Node>> key = new Pair<>(currentNode.getID(), connectedNodes);
                if(DatabaseManager.getINSTANCE().getNetworkMap().getAvailability(key) < Math.ceil(demand.getBandwidth() / DatabaseManager.getINSTANCE().getNetworkMap().getBandwidthUnit(key)) + PropertyManager.getValueAsDouble("NetworkComponentMargin")) {
                    return false;
                }
                connectedNodes.clear();
                prevNode = currentNode;
                currentNode = node;
            }
        }
        return true;
    }
    
    @Override
    public String toString() {
        StringBuilder buffer = new StringBuilder(50);
        buffer.append("[");
        buffer.append(new Throwable().getStackTrace()[0].getClassName());
        buffer.append("]");
        return buffer.toString();
    }
    
}
